﻿using System;
using System.Linq;
using Microsoft.SharePoint;
using QASPRibbon.Commands;
using QASPRibbon.Controls;
using QASPRibbon.Helpers;

namespace QASPRibbon
{
    /// <summary>
    /// Manage ribbon creation from feature receivers
    /// </summary>
    public class RibbonCustomAction
    {
        /// <summary>
        /// Creates a user custom action for ribbon creation and management.
        /// Custom action is something like a container for your customizations.
        /// </summary>
        public RibbonCustomAction()
        {
            RibbonXML = string.Empty;
            RibbonCommandsXML = string.Empty;
            RibbonTemplatesXML = "<root></root>";
        }

        /// <summary>
        /// Removes ribbon element with specified id (it can be control, control group, tab, whatever..)
        /// </summary>
        /// <param name="controlId">Id of control, which will be deleted</param>
        public void RemoveRibbonElement(string controlId)
        {
            RibbonXML = ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(controlId, string.Empty));
        }

        /// <summary>
        /// Provision all pending customizations to specified web.
        /// Customizations will be shown on specified list pages, for lists with specified ID (this can be your custom list template ID), 
        /// to users with appropriate rights.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="userCustomActions">Collection of custom actions of web or list (web.UserCustomActions or list.UserCustomActions)</param>
        /// <param name="templateId">Custom list template Id, for which ribbon elements will be registered</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <param name="rights">Rights user needs to access the ribbon</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPUserCustomActionCollection userCustomActions, string templateId, ListForms whichForms, SPBasePermissions? rights)
        {
            var customAction = userCustomActions.Add();

            customAction.Name = string.Format("QASPRibbon._{0}._{1}", featureUniqueGuid.ToString().Replace("-", ""), Guid.NewGuid().ToString().Replace("-", ""));
            customAction.Location = GetRibbonLocationByListForms(whichForms);
            customAction.CommandUIExtension = XmlHelper.Current.GetCommandUIExtensionXML(RibbonXML, RibbonCommandsXML, RibbonTemplatesXML);
            if (customAction.Scope != SPUserCustomActionScope.List)
            {
                if (String.IsNullOrEmpty(templateId) || templateId == ((int)ListTypes.All).ToString())
                {
                    customAction.RegistrationType = SPUserCustomActionRegistrationType.ContentType;
                    customAction.RegistrationId = "0x";
                }
                else if (templateId != ((int)ListTypes.None).ToString())
                {
                    customAction.RegistrationType = SPUserCustomActionRegistrationType.List;
                    customAction.RegistrationId = templateId;
                }
            }
            if (rights.HasValue)
                customAction.Rights = rights.Value;

            customAction.Update();

            return customAction.Id;
        }

        /// <summary>
        /// Remove all ribbon customizations from specified site. Usually done in FeatureDeactivating method.
        /// </summary>
        /// <param name="site">SPSite object, from which customizations will be removed.</param>
        /// <param name="featureScopedGuid">Guid, which was previosly passed into ribbon creation methods.</param>
        public static void RemoveAllCustomizations(SPSite site, Guid featureScopedGuid)
        {
            for (int i = site.UserCustomActions.Count - 1; i >= 0; i--)
            {
                var customAction = site.UserCustomActions.ElementAt(i);

                if (customAction.Name.StartsWith(string.Format("QASPRibbon._{0}", featureScopedGuid.ToString().Replace("-", ""))))
                {
                    customAction.Delete();
                }
            }
        }

        /// <summary>
        /// Remove all ribbon customizations from specified web. Usually done in FeatureDeactivating method.
        /// </summary>
        /// <param name="web">SPWeb object, from which customizations will be removed.</param>
        /// <param name="featureScopedGuid">Guid, which was previosly passed into ribbon creation methods.</param>
        public static void RemoveAllCustomizations(SPWeb web, Guid featureScopedGuid)
        {
            bool isCollectionChanged = false;
            for (int i = web.UserCustomActions.Count - 1; i >= 0; i--)
            {
                var customAction = web.UserCustomActions.ElementAt(i);

                if (customAction.Name.StartsWith(string.Format("QASPRibbon._{0}", featureScopedGuid.ToString().Replace("-", ""))))
                {
                    isCollectionChanged = true;
                    customAction.Delete();
                }
            }

            if (isCollectionChanged)
                web.Update();
        }

        /// <summary>
        /// Remove all ribbon customizations from specified list. Usually done in FeatureDeactivating method.
        /// </summary>
        /// <param name="list"></param>
        /// <param name="featureScopedGuid">Guid, which was previosly passed into ribbon creation methods.</param>
        public static void RemoveAllCustomizations(SPList list, Guid featureScopedGuid)
        {
            bool isCollectionChanged = false;
            for (int i = list.UserCustomActions.Count - 1; i >= 0; i--)
            {
                var customAction = list.UserCustomActions.ElementAt(i);

                if (customAction.Name.StartsWith(string.Format("QASPRibbon._{0}", featureScopedGuid.ToString().Replace("-", ""))))
                {
                    isCollectionChanged = true;
                    customAction.Delete();
                }
            }

            if (isCollectionChanged)
                list.Update();
        }

        #region Private

        private string RibbonXML { get; set; }
        private string RibbonCommandsXML { get; set; }
        private string RibbonTemplatesXML { get; set; }

        private static string GetCommandsXML(RibbonElement definition)
        {
            var commands = new RibbonCommandRepository();
            commands.AddCommands(definition);
            var commandsXML = commands.GetCommands()
                .Aggregate(string.Empty, (current, command) => current + XmlHelper.Current.GetCommandUIHandlerXML(command));

            return string.Format("<root>{0}</root>", commandsXML);
        }

        private static string GetRibbonLocationByListForms(ListForms whichForms)
        {
            switch (whichForms)
            {
                case ListForms.ListView:
                case ListForms.NewForm:
                case ListForms.EditForm:
                case ListForms.DisplayForm:
                    return "CommandUI.Ribbon." + whichForms;
                case ListForms.All:
                    return "CommandUI.Ribbon";
                default:
                    throw new Exception("Impossible list forms value.");
            }
        }

        private static string ConcatXML(string xml1, string xml2)
        {
            return
                string.Format("<root>{0}{1}</root>",
                              xml1
                                  .Replace("<root>", string.Empty)
                                  .Replace("</root>", string.Empty),
                              xml2
                                  .Replace("<root>", string.Empty)
                                  .Replace("</root>", string.Empty));
        }

        #endregion


        /// <summary>
        /// Adds control to existing control group
        /// </summary>
        /// <param name="control">Definition of ribbon control.</param>
        /// <param name="controlGroupId">Id of a control group, where the control will be inserted into. For example: "Ribbon.ListItem.New"</param>
        /// <param name="controlSequence">Sequence value for this control to position it inside the group.</param>
        public void AddControl(RibbonControl control, string controlGroupId, int controlSequence)
        {
            control.Sequence = controlSequence;
            control.NameSpace = controlGroupId;
            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    controlGroupId + ".Controls._children",
                    control.AsXElement().ToString()));

            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(control));
        }

        /// <summary>
        /// Replaces control with custom one.
        /// </summary>
        /// <param name="control">Definition of ribbon control.</param>
        /// <param name="controlId">Id of the old control, which will be replaced</param>
        /// <param name="controlSequence">Sequence value for this control to position it inside the group.</param>
        public void ReplaceControl(RibbonControl control, string controlId, int controlSequence)
        {
            control.Sequence = controlSequence;
            control.NameSpace = controlId.Substring(0, controlId.LastIndexOf('.'));
            control.Id = controlId.Substring(controlId.LastIndexOf('.') + 1);
            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    controlId,
                    control.AsXElement().ToString()));

            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(control));
        }

        /// <summary>
        /// Registers custom ribbon controls group for a list at specified SPWeb.
        /// </summary>
        /// <param name="group">Definition of the ribbon group.</param>
        /// <param name="tabId">Id of parent tab for this group. For example, it could be "Ribbon.ListItem".</param>
        /// <param name="sequence">Sequence for the group, for positioning it within the tab.</param>
        public void AddControlGroup(RibbonGroup group, string tabId, int sequence)
        {
            var groupXML = group.AsXElement(tabId, sequence).ToString();
            var fragments = groupXML
                .Replace("<root>", string.Empty)
                .Replace("</root>", string.Empty)
                .Split(new[] { "</Scaling>" }, StringSplitOptions.None);
            var scalingFragment = fragments[0].Replace("<Scaling", "<root") + "</root>";
            var groupFragment = fragments[1];

            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    tabId + ".Groups._children",
                    groupFragment));

            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    tabId + ".Scaling._children",
                    scalingFragment));

            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(group));
            RibbonTemplatesXML = ConcatXML(RibbonTemplatesXML, group.TemplateXml);
        }

        /// <summary>
        /// Registers custom ribbon controls group for a list at specified SPWeb, replacing one of the existing groups.
        /// </summary>
        /// <param name="group">Definition of the ribbon group.</param>
        /// <param name="oldGroupId">Valid existing ribbon control group id, for group which will be replaced.</param>
        /// <param name="sequence">Sequence for the group, for positioning it within the tab.</param>
        public void ReplaceControlGroup(RibbonGroup group, string oldGroupId, int sequence)
        {
            // bad code, really
            // TODO: refactoring
            group.Sequence = sequence;
            group.NameSpace = oldGroupId.Substring(0, oldGroupId.LastIndexOf('.'));
            group.Id = oldGroupId.Substring(oldGroupId.LastIndexOf('.') + 1);
            var groupXML = group.AsXElement().ToString();

            var fragments = groupXML
                .Replace("<root>", string.Empty)
                .Replace("</root>", string.Empty)
                .Split(new[] { "</Scaling>" }, StringSplitOptions.None);

            var scalingFragment = fragments[0].Replace("<Scaling", "<root") + "</root>";
            var groupFragment = fragments[1];

            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    oldGroupId,
                    groupFragment));

            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    oldGroupId.Substring(0, oldGroupId.LastIndexOf('.')) + ".Scaling._children",
                    scalingFragment));

            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(group));
        }

        ///<summary>
        /// Registers custom ribbon tab for a list at specified SPWeb.
        /// </summary>
        /// <param name="tab">Definition of the ribbon tab.</param>
        public void AddTab(RibbonTab tab)
        {
            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    "Ribbon.Tabs._children",
                    tab.AsXElement().ToString()));
            RibbonTemplatesXML = ConcatXML(RibbonTemplatesXML, tab.TemplatesXml);
            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(tab));
        }

        /// <summary>
        /// Registers custom ribbon tab for a list at specified SPWeb, replacing one of the existing tabs.
        /// </summary>
        /// <param name="tab">Definition of the ribbon tab.</param>
        /// <param name="oldTabId">Valid existing ribbon tab id, for tab which will be replaced.</param>
        public void ReplaceTab(RibbonTab tab, string oldTabId)
        {
            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    oldTabId,
                    tab.AsXElement().ToString()));
            RibbonTemplatesXML = ConcatXML(RibbonTemplatesXML, tab.TemplatesXml);
            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(tab));
        }

        /// <summary>
        /// Adds custom tab to specified contextual tab group
        /// </summary>
        /// <param name="tab">Definition of the ribbon tab.</param>
        /// <param name="contextualGroupId">Valid existing ribbon contextual tab group id, inside which the tab will be added. </param>
        public void AddTabToContextualGroup(RibbonTab tab, string contextualGroupId)
        {
            RibbonXML =
                ConcatXML(RibbonXML, XmlHelper.Current.GetCommandUIDefinitionXML(
                    contextualGroupId + "._children",
                    tab.AsXElement().ToString()));
            RibbonTemplatesXML = ConcatXML(RibbonTemplatesXML, tab.TemplatesXml);
            RibbonCommandsXML = ConcatXML(RibbonCommandsXML, GetCommandsXML(tab));
        }

        private Guid ProvisionSite(Guid featureUniqueGuid, SPSite site, string templateId, ListForms whichForms, SPBasePermissions? rights)
        {
            return Provision(featureUniqueGuid, site.UserCustomActions, templateId, whichForms, rights);
        }

        private Guid ProvisionWeb(Guid featureUniqueGuid, SPWeb web, string templateId, ListForms whichForms, SPBasePermissions? rights)
        {
            var id = Provision(featureUniqueGuid, web.UserCustomActions, templateId, whichForms, rights);
            web.Update();
            return id;
        }

        private Guid ProvisionList(Guid featureUniqueGuid, SPList list, string templateId, ListForms whichForms, SPBasePermissions? rights)
        {
            var id = Provision(featureUniqueGuid, list.UserCustomActions, templateId, whichForms, rights);
            list.Update();
            return id;
        }

        #region List

        /// <summary>
        /// Provision all pending customizations to specified list.
        /// Customizations will be shown on all list pages.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RemoveAllCustomizations"/> method.</param>
        /// <param name="list"></param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPList list)
        {
            return ProvisionList(featureUniqueGuid, list, string.Empty, ListForms.All, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified list.
        /// Customizations will be shown on specified list pages for lists with specified ID.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="list">SPList, where a ribbon will be added</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPList list, ListForms whichForms)
        {
            return ProvisionList(featureUniqueGuid, list, string.Empty, whichForms, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified web.
        /// Customizations will be shown on specified list pages, only to users with appropriate rights.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="list">SPList, where a ribbon will be added</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <param name="rights">Minimal privilegies, needed to see the ribbon elements</param>
        /// <returns>Id of Provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPList list, ListForms whichForms, SPBasePermissions rights)
        {
            return ProvisionList(featureUniqueGuid, list, string.Empty, whichForms, rights);
        }

        #endregion

        #region Web

        /// <summary>
        /// Provision all pending customizations to specified web.
        /// Customizations will be shown on all list pages.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RemoveAllCustomizations"/> method.</param>
        /// <param name="web">Web, where ribbon will be added</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPWeb web)
        {
            return ProvisionWeb(featureUniqueGuid, web, string.Empty, ListForms.All, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified web.
        /// Customizations will be shown on all pages for lists with specified ID.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="web">SPWeb, where a ribbon will be added</param>
        /// <param name="listType">One of standard list types, for which ribbon elements will be registered</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPWeb web, ListTypes listType)
        {
            return ProvisionWeb(featureUniqueGuid, web, ((int)listType).ToString(), ListForms.All, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified web.
        /// Customizations will be shown on specified list pages for lists with specified ID.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="site">SPWeb, where a ribbon will be added</param>
        /// <param name="listType">One of standard list types, for which ribbon elements will be registered</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPWeb web, ListTypes listType, ListForms whichForms)
        {
            return ProvisionWeb(featureUniqueGuid, web, ((int)listType).ToString(), whichForms, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified web.
        /// Customizations will be shown on specified list pages, only to users with appropriate rights.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="site">SPWeb, where a ribbon will be added</param>
        /// <param name="listType">One of standard list types, for which ribbon elements will be registered</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <param name="rights">Minimal privilegies, needed to see the ribbon elements</param>
        /// <returns>Id of Provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPWeb web, ListTypes listType, ListForms whichForms, SPBasePermissions rights)
        {
            return ProvisionWeb(featureUniqueGuid, web, ((int)listType).ToString(), whichForms, rights);
        }

        #endregion

        #region Site
        /// <summary>
        /// Provision all pending customizations to specified site collection.
        /// Customizations will be shown on all list pages.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RemoveAllCustomizations"/> method.</param>
        /// <param name="site">Site collection, to which the ribbon customizations will be provisioned</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPSite site)
        {
            return ProvisionSite(featureUniqueGuid, site, string.Empty, ListForms.All, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified site collection.
        /// Customizations will be shown on all pages for lists with specified ID.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="site">Site collection, to which the ribbon customizations will be provisioned</param>
        /// <param name="listType">One of standard list types, for which ribbon elements will be registered</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPSite site, ListTypes listType)
        {
            return ProvisionSite(featureUniqueGuid, site, ((int)listType).ToString(), ListForms.All, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified site collection.
        /// Customizations will be shown on specified list pages for lists with specified ID.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="site">Site collection, to which the ribbon customizations will be provisioned</param>
        /// <param name="listType">One of standard list types, for which ribbon elements will be registered</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <returns>Id of provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPSite site, ListTypes listType, ListForms whichForms)
        {
            return ProvisionSite(featureUniqueGuid, site, ((int)listType).ToString(), whichForms, null);
        }

        /// <summary>
        /// Provision all pending customizations to specified site collection.
        /// Customizations will be shown on specified list pages, only to users with appropriate rights.
        /// </summary>
        /// <param name="featureUniqueGuid">Guid, needed for feature-scoped cleanup in FeatureDeactivating using <see cref="RibbonCustomAction.RemoveAllCustomizations"/> method.</param>
        /// <param name="site">Site collection, to which the ribbon customizations will be provisioned</param>
        /// <param name="listType">One of standard list types, for which ribbon elements will be registered</param>
        /// <param name="whichForms">List forms, which will display the ribbon</param>
        /// <param name="rights">Minimal privilegies, needed to see the ribbon elements</param>
        /// <returns>Id of Provisioned custom action</returns>
        public Guid Provision(Guid featureUniqueGuid, SPSite site, ListTypes listType, ListForms whichForms, SPBasePermissions rights)
        {
            return ProvisionSite(featureUniqueGuid, site, ((int)listType).ToString(), whichForms, rights);
        }
        #endregion
    }
}
